// 版权所有2010 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package net

import (
	"context"
	"internal/nettrace"
	"syscall"
	"time"
)

// defaultTCPKeepAlive是TCPKeepAlive乘以
// 参见golang的默认常量值。org/issue/31510 
const (
	defaultTCPKeepAlive = 15 * time.Second
)

// 拨号程序包含连接地址的选项。
// 
// 每个字段的零值相当于在没有该选项的情况下拨打
// 。因此，使用拨号器
// /的零值拨号相当于只调用拨号功能。
// 
// 同时调用拨号程序的方法是安全的。
type Dialer struct {
	// 超时是拨号等待
	// 连接完成的最长时间。如果还设置了截止日期，它可能会在
	// /之前失败。
	// 
	// 默认为无超时。
	// 
	// 当使用TCP并拨打具有多个IP地址的主机名
	// 时，超时时间可以在它们之间分配。
	// 
	// 无论是否有超时，操作系统都可能强制执行
	// 它自己较早的超时。例如，TCP超时通常在3分钟左右。
	Timeout time.Duration

	// 截止日期是拨号
	// 将失败的绝对时间点。如果设置了超时，它可能会更早失败。
	// 零表示没有截止日期，或依赖于操作系统
	// 与超时选项一样。
	Deadline time.Time

	// LocalAddr是拨打
	// 地址时使用的本地地址。该地址必须与正在拨打的
	// 网络兼容。
	// 如果为零，则自动选择本地地址。
	LocalAddr Addr

	// 之前启用的双堆栈RFC 6555快速回退
	// 支持，也称为“快乐眼球”，其中IPv4是
	// 如果IPv6出现配置错误，将很快尝试，
	// 挂起。
	// 
	// 已弃用：默认情况下启用快速回退。要禁用，请将FallbackDelay设置为负值。
	DualStack bool

	// FallbackDelay指定在生成RFC 6555快速回退连接之前等待的时间长度。也就是说，这个
	// 是指在假设IPv6配置错误并返回到
	// IPv4之前等待IPv6成功的时间量。
	// 
	// 如果为零，则使用300毫秒的默认延迟。
	// 负值将禁用快速回退支持。
	FallbackDelay time.Duration

	// KeepAlive指定活动网络连接的保持活动
	// 探测之间的间隔。
	// 如果为零，则发送的keep alive Probe带有默认值
	// （当前为15秒），前提是协议和操作
	// 系统支持。不支持keep alives的网络协议或操作系统忽略此字段。
	// 如果为负，则禁用保持活动的探针。
	KeepAlive time.Duration

	// 解析器可选择指定要使用的备用解析器。
	Resolver *Resolver

	// Cancel是一个可选通道，它的关闭表示
	// 应该取消拨号。并非所有类型的拨号盘都支持
	// 取消。
	// 
	// 已弃用：改用DialContext。
	Cancel <-chan struct{}

	// 如果Control不是nil，则在创建网络
	// 连接后，但在实际拨号之前调用它。
	// 
	// 传递给控制方法的网络和地址参数不是
	// 必须是传递给拨号的参数。例如，通过“tcp”拨号
	// 将导致用“tcp4”或“tcp6”调用控制函数。
	Control func(network, address string, c syscall.RawConn) error
}

func (d *Dialer) dualStack() bool { return d.FallbackDelay >= 0 }

func minNonzeroTime(a, b time.Time) time.Time {
	if a.IsZero() {
		return b
	}
	if b.IsZero() || a.Before(b) {
		return a
	}
	return b
}

// deadline返回以下最早的值：
// /-now+Timeout 
// /-d.deadline 
// /-上下文的最后期限
// 或零，如果没有设置超时、最后期限或上下文的最后期限。
func (d *Dialer) deadline(ctx context.Context, now time.Time) (earliest time.Time) {
	if d.Timeout != 0 { // 包括负数，由于历史原因
		earliest = now.Add(d.Timeout)
	}
	if d, ok := ctx.Deadline(); ok {
		earliest = minNonzeroTime(earliest, d)
	}
	return minNonzeroTime(earliest, d.Deadline)
}

func (d *Dialer) resolver() *Resolver {
	if d.Resolver != nil {
		return d.Resolver
	}
	return DefaultResolver
}

// partialDeadline返回单个地址使用的截止日期，
// 当多个地址挂起时。
func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, error) {
	if deadline.IsZero() {
		return deadline, nil
	}
	timeRemaining := deadline.Sub(now)
	if timeRemaining <= 0 {
		return time.Time{}, errTimeout
	}
	// 暂时为每个剩余地址分配相等的时间。
	timeout := timeRemaining / time.Duration(addrsRemaining)
	// 如果每个地址的时间太短，请从列表末尾偷取。
	const saneMinimum = 2 * time.Second
	if timeout < saneMinimum {
		if timeRemaining < saneMinimum {
			timeout = timeRemaining
		} else {
			timeout = saneMinimum
		}
	}
	return now.Add(timeout), nil
}

func (d *Dialer) fallbackDelay() time.Duration {
	if d.FallbackDelay > 0 {
		return d.FallbackDelay
	} else {
		return 300 * time.Millisecond
	}
}

func parseNetwork(ctx context.Context, network string, needsProto bool) (afnet string, proto int, err error) {
	i := last(network, ':')
	if i < 0 { // 无冒号
		switch network {
		case "tcp", "tcp4", "tcp6":
		case "udp", "udp4", "udp6":
		case "ip", "ip4", "ip6":
			if needsProto {
				return "", 0, UnknownNetworkError(network)
			}
		case "unix", "unixgram", "unixpacket":
		default:
			return "", 0, UnknownNetworkError(network)
		}
		return network, 0, nil
	}
	afnet = network[:i]
	switch afnet {
	case "ip", "ip4", "ip6":
		protostr := network[i+1:]
		proto, i, ok := dtoi(protostr)
		if !ok || i != len(protostr) {
			proto, err = lookupProtocol(ctx, protostr)
			if err != nil {
				return "", 0, err
			}
		}
		return afnet, proto, nil
	}
	return "", 0, UnknownNetworkError(network)
}

// resolveAddrList使用提示解析addr，并返回
// 地址列表。当错误为
// 零。
func (r *Resolver) resolveAddrList(ctx context.Context, op, network, addr string, hint Addr) (addrList, error) {
	afnet, _, err := parseNetwork(ctx, network, true)
	if err != nil {
		return nil, err
	}
	if op == "dial" && addr == "" {
		return nil, errMissingAddress
	}
	switch afnet {
	case "unix", "unixgram", "unixpacket":
		addr, err := ResolveUnixAddr(afnet, addr)
		if err != nil {
			return nil, err
		}
		if op == "dial" && hint != nil && addr.Network() != hint.Network() {
			return nil, &AddrError{Err: "mismatched local address type", Addr: hint.String()}
		}
		return addrList{addr}, nil
	}
	addrs, err := r.internetAddrList(ctx, afnet, addr)
	if err != nil || op != "dial" || hint == nil {
		return addrs, err
	}
	var (
		tcp      *TCPAddr
		udp      *UDPAddr
		ip       *IPAddr
		wildcard bool
	)
	switch hint := hint.(type) {
	case *TCPAddr:
		tcp = hint
		wildcard = tcp.isWildcard()
	case *UDPAddr:
		udp = hint
		wildcard = udp.isWildcard()
	case *IPAddr:
		ip = hint
		wildcard = ip.isWildcard()
	}
	naddrs := addrs[:0]
	for _, addr := range addrs {
		if addr.Network() != hint.Network() {
			return nil, &AddrError{Err: "mismatched local address type", Addr: hint.String()}
		}
		switch addr := addr.(type) {
		case *TCPAddr:
			if !wildcard && !addr.isWildcard() && !addr.IP.matchAddrFamily(tcp.IP) {
				continue
			}
			naddrs = append(naddrs, addr)
		case *UDPAddr:
			if !wildcard && !addr.isWildcard() && !addr.IP.matchAddrFamily(udp.IP) {
				continue
			}
			naddrs = append(naddrs, addr)
		case *IPAddr:
			if !wildcard && !addr.isWildcard() && !addr.IP.matchAddrFamily(ip.IP) {
				continue
			}
			naddrs = append(naddrs, addr)
		}
	}
	if len(naddrs) == 0 {
		return nil, &AddrError{Err: errNoSuitableAddress.Error(), Addr: hint.String()}
	}
	return naddrs, nil
}

// 拨号连接到指定网络上的地址。已知网络有“tcp”、“tcp4”（仅限IPv4）、“tcp6”（仅限IPv6）、udp”、“udp4”（仅限IPv4）、“udp6”（仅限IPv6）、“udp6”（仅限IPv6）、“ip”、“ip4”和“ip6”（仅限IPv6）、“unix”、“unixgram”和“unixpacket”。wen jian efg
// 主机必须是文字IP地址，或者可以是
// 解析为IP地址的主机名。
// 端口必须是文本端口号或服务名称。
// 如果主机是一个文本IPv6地址，则必须将其括在方括号中，如“[2001:db8:：1]：80”或“[fe80:：1%区域]：80”。
// 该区域指定RFC 4007中定义的文本IPv6地址的范围。
// 函数JoinHostPort和SplitHostPort以这种形式操作一对
// 主机和端口。
// 当使用TCP时，主机解析为多个IP地址，
// 拨号将按顺序尝试每个IP地址，直到其中一个成功。
// 
// 示例：
// 拨号（“tcp”，“golang.org:http”）
// 拨号（“tcp”，“192.0.2.1:http”）
// 拨号（“tcp”，“198.51.100.1:80”）
// 地址或带区域的文本IPv6地址。
// 这取决于每个操作系统，操作系统
// 如何使用非已知的协议号，如“0”或“255”。
// 
// 示例：
// 拨号（“ip4:1”、“192.0.2.1”）
// 拨号（“ip6:ipv6 icmp”、“2001:db8:：1”）
// 拨号（“ip6:58”、“fe80:：1%lo0”）
// 
// 对于TCP、UDP和IP网络，如果主机是空的，或者是字面上的
// TCP和UDP，“0.0.0.0:80”或“[：：]：80”，“0.0.0.0”或：“对于IP，本地系统为
// 假定。
// 
// 对于Unix网络，地址必须是文件系统路径。
func Dial(network, address string) (Conn, error) {
	var d Dialer
	return d.Dial(network, address)
}

// DialTimeout的行为类似于Dial，但需要超时。
// 
// 如果需要，超时包括名称解析。
// 使用TCP时，地址参数中的主机解析为
// 多个IP地址，超时时间分布在每个连续的
// 拨号上，这样每个拨号都会得到
// 连接时间的适当部分。
// 
// 有关网络和地址的说明，请参阅func Dial 
// 参数。
func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {
	d := Dialer{Timeout: timeout}
	return d.Dial(network, address)
}

// 系统拨号程序包含拨号的参数和配置。
type sysDialer struct {
	Dialer
	network, address string
}

// 拨号连接到指定网络上的地址。
// 
// 有关网络和地址的说明，请参阅func Dial 
// 参数。
// 
// 拨号使用上下文。内部背景；要指定上下文，请使用
// DialContext。
func (d *Dialer) Dial(network, address string) (Conn, error) {
	return d.DialContext(context.Background(), network, address)
}

// DialContext使用提供的上下文连接到指定网络上的地址。
// 
// 提供的上下文必须为非零。如果上下文在
// 连接完成之前过期，则返回错误。一旦成功连接
// 后，上下文的任何过期都不会影响
// 连接。
// 
// 当使用TCP时，地址参数中的主机解析为多个
// 网络地址，任何拨号超时（从d.timeout或ctx开始）都会分布在每个连续拨号上，这样每个拨号都会得到一个适当的
// 连接时间的一小部分。
// 例如，如果一台主机有4个IP地址，超时时间为1分钟，则在尝试下一个地址之前，将给每个地址的连接15秒时间来完成。
// 
// 有关网络和地址的说明，请参阅func Dial 
// 参数。
func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) {
	if ctx == nil {
		panic("nil context")
	}
	deadline := d.deadline(ctx, time.Now())
	if !deadline.IsZero() {
		if d, ok := ctx.Deadline(); !ok || deadline.Before(d) {
			subCtx, cancel := context.WithDeadline(ctx, deadline)
			defer cancel()
			ctx = subCtx
		}
	}
	if oldCancel := d.Cancel; oldCancel != nil {
		subCtx, cancel := context.WithCancel(ctx)
		defer cancel()
		go func() {
			select {
			case <-oldCancel:
				cancel()
			case <-subCtx.Done():
			}
		}()
		ctx = subCtx
	}

	// 在解析过程中对nettrace（如果有）进行阴影处理，这样就不会为DNS查找触发连接事件。
	resolveCtx := ctx
	if trace, _ := ctx.Value(nettrace.TraceKey{}).(*nettrace.Trace); trace != nil {
		shadow := *trace
		shadow.ConnectStart = nil
		shadow.ConnectDone = nil
		resolveCtx = context.WithValue(resolveCtx, nettrace.TraceKey{}, &shadow)
	}

	addrs, err := d.resolver().resolveAddrList(resolveCtx, "dial", network, address, d.LocalAddr)
	if err != nil {
		return nil, &OpError{Op: "dial", Net: network, Source: nil, Addr: nil, Err: err}
	}

	sd := &sysDialer{
		Dialer:  *d,
		network: network,
		address: address,
	}

	var primaries, fallbacks addrList
	if d.dualStack() && network == "tcp" {
		primaries, fallbacks = addrs.partition(isIPv4)
	} else {
		primaries = addrs
	}

	var c Conn
	if len(fallbacks) > 0 {
		c, err = sd.dialParallel(ctx, primaries, fallbacks)
	} else {
		c, err = sd.dialSerial(ctx, primaries)
	}
	if err != nil {
		return nil, err
	}

	if tc, ok := c.(*TCPConn); ok && d.KeepAlive >= 0 {
		setKeepAlive(tc.fd, true)
		ka := d.KeepAlive
		if d.KeepAlive == 0 {
			ka = defaultTCPKeepAlive
		}
		setKeepAlivePeriod(tc.fd, ka)
		testHookSetKeepAlive(ka)
	}
	return c, nil
}

// dialParallel与dialSerial的两个副本进行竞争，使第一个副本领先一步。它返回第一个建立的连接，
// 关闭其他连接。否则，它将从第一个
// 主地址返回一个错误。
func (sd *sysDialer) dialParallel(ctx context.Context, primaries, fallbacks addrList) (Conn, error) {
	if len(fallbacks) == 0 {
		return sd.dialSerial(ctx, primaries)
	}

	returned := make(chan struct{})
	defer close(returned)

	type dialResult struct {
		Conn
		error
		primary bool
		done    bool
	}
	results := make(chan dialResult) // 无缓冲

	startRacer := func(ctx context.Context, primary bool) {
		ras := primaries
		if !primary {
			ras = fallbacks
		}
		c, err := sd.dialSerial(ctx, ras)
		select {
		case results <- dialResult{Conn: c, error: err, primary: primary, done: true}:
		case <-returned:
			if c != nil {
				c.Close()
			}
		}
	}

	var primary, fallback dialResult

	// 启动主赛车手。
	primaryCtx, primaryCancel := context.WithCancel(ctx)
	defer primaryCancel()
	go startRacer(primaryCtx, true)

	// 为后备赛车手启动计时器。
	fallbackTimer := time.NewTimer(sd.fallbackDelay())
	defer fallbackTimer.Stop()

	for {
		select {
		case <-fallbackTimer.C:
			fallbackCtx, fallbackCancel := context.WithCancel(ctx)
			defer fallbackCancel()
			go startRacer(fallbackCtx, false)

		case res := <-results:
			if res.error == nil {
				return res.Conn, nil
			}
			if res.primary {
				primary = res
			} else {
				fallback = res
			}
			if primary.done && fallback.done {
				return nil, primary.error
			}
			if res.primary && fallbackTimer.Stop() {
				// 如果我们能够停止计时器，这意味着它正在运行（尚未启动回退），但
				// 我们只是在主路径上出错，所以立即启动回退（0纳秒）。
				fallbackTimer.Reset(0)
			}
		}
	}
}

// dialSerial按顺序连接到地址列表，返回
// 第一次成功连接或第一次错误。
func (sd *sysDialer) dialSerial(ctx context.Context, ras addrList) (Conn, error) {
	var firstErr error // 第一个地址的错误最相关。

	for i, ra := range ras {
		select {
		case <-ctx.Done():
			return nil, &OpError{Op: "dial", Net: sd.network, Source: sd.LocalAddr, Addr: ra, Err: mapErr(ctx.Err())}
		default:
		}

		dialCtx := ctx
		if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
			partialDeadline, err := partialDeadline(time.Now(), deadline, len(ras)-i)
			if err != nil {
				// 没时间了。
				if firstErr == nil {
					firstErr = &OpError{Op: "dial", Net: sd.network, Source: sd.LocalAddr, Addr: ra, Err: err}
				}
				break
			}
			if partialDeadline.Before(deadline) {
				var cancel context.CancelFunc
				dialCtx, cancel = context.WithDeadline(ctx, partialDeadline)
				defer cancel()
			}
		}

		c, err := sd.dialSingle(dialCtx, ra)
		if err == nil {
			return c, nil
		}
		if firstErr == nil {
			firstErr = err
		}
	}

	if firstErr == nil {
		firstErr = &OpError{Op: "dial", Net: sd.network, Source: nil, Addr: nil, Err: errMissingAddress}
	}
	return nil, firstErr
}

// dialSingle尝试建立并返回到目标地址的单个连接。
func (sd *sysDialer) dialSingle(ctx context.Context, ra Addr) (c Conn, err error) {
	trace, _ := ctx.Value(nettrace.TraceKey{}).(*nettrace.Trace)
	if trace != nil {
		raStr := ra.String()
		if trace.ConnectStart != nil {
			trace.ConnectStart(sd.network, raStr)
		}
		if trace.ConnectDone != nil {
			defer func() { trace.ConnectDone(sd.network, raStr, err) }()
		}
	}
	la := sd.LocalAddr
	switch ra := ra.(type) {
	case *TCPAddr:
		la, _ := la.(*TCPAddr)
		c, err = sd.dialTCP(ctx, la, ra)
	case *UDPAddr:
		la, _ := la.(*UDPAddr)
		c, err = sd.dialUDP(ctx, la, ra)
	case *IPAddr:
		la, _ := la.(*IPAddr)
		c, err = sd.dialIP(ctx, la, ra)
	case *UnixAddr:
		la, _ := la.(*UnixAddr)
		c, err = sd.dialUnix(ctx, la, ra)
	default:
		return nil, &OpError{Op: "dial", Net: sd.network, Source: la, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: sd.address}}
	}
	if err != nil {
		return nil, &OpError{Op: "dial", Net: sd.network, Source: la, Addr: ra, Err: err} // c是包含nil指针的非nil接口
	}
	return c, nil
}

// ListenConfig包含监听地址的选项。
type ListenConfig struct {
	// 如果Control不是nil，则在创建网络
	// 连接后，但在将其绑定到操作系统之前调用它。
	// 
	// 传递给控制方法的网络和地址参数不一定是传递给侦听的参数。例如，将“tcp”传递给
	// Listen将导致用“tcp4”或“tcp6”调用控制函数。
	Control func(network, address string, c syscall.RawConn) error

	// KeepAlive指定此侦听器接受的网络连接的保持活动期。
	// 如果为零，则在协议
	// 和操作系统支持的情况下启用keep alives。不支持keep alives的网络协议或操作系统
	// 忽略此字段。
	// 如果为负数，则禁用保留生命。
	KeepAlive time.Duration
}

// 在本地网络地址上收听广播。
// 
// 有关网络和地址的描述，请参阅func Listen 
// 参数。
func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) {
	addrs, err := DefaultResolver.resolveAddrList(ctx, "listen", network, address, nil)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
	}
	sl := &sysListener{
		ListenConfig: *lc,
		network:      network,
		address:      address,
	}
	var l Listener
	la := addrs.first(isIPv4)
	switch la := la.(type) {
	case *TCPAddr:
		l, err = sl.listenTCP(ctx, la)
	case *UnixAddr:
		l, err = sl.listenUnix(ctx, la)
	default:
		return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
	}
	if err != nil {
		return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: err} // l是包含nil指针的非nil接口
	}
	return l, nil
}

// ListenPacket在本地网络地址上宣布。
// 
// 有关网络和地址的说明，请参见func ListenPackage 
// 参数。
func (lc *ListenConfig) ListenPacket(ctx context.Context, network, address string) (PacketConn, error) {
	addrs, err := DefaultResolver.resolveAddrList(ctx, "listen", network, address, nil)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
	}
	sl := &sysListener{
		ListenConfig: *lc,
		network:      network,
		address:      address,
	}
	var c PacketConn
	la := addrs.first(isIPv4)
	switch la := la.(type) {
	case *UDPAddr:
		c, err = sl.listenUDP(ctx, la)
	case *IPAddr:
		c, err = sl.listenIP(ctx, la)
	case *UnixAddr:
		c, err = sl.listenUnixgram(ctx, la)
	default:
		return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
	}
	if err != nil {
		return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: err} // c是包含nil指针的非nil接口
	}
	return c, nil
}

// sysListener包含侦听器的参数和配置。
type sysListener struct {
	ListenConfig
	network, address string
}

// 在本地网络地址上收听广播。
// 
// 网络必须是“tcp”、“tcp4”、“tcp6”、“unix”或“unixpacket”。
// 
// 对于TCP网络，如果地址参数中的主机为空或
// 未指定的文本IP地址，则侦听本地系统的所有可用
// 单播和选播IP地址。
// 要仅使用IPv4，请使用网络“tcp4”。
// 该地址可以使用主机名，但不建议使用，因为它最多会为主机的一个IP 
// 地址创建一个侦听器。
// 如果地址参数中的端口为空或“0”，如
// “127.0.0.1:”或“[：：1]：0”，则会自动选择端口号。
// 侦听器的Addr方法可用于发现所选的
// 端口。
// 
// 有关网络和地址的说明，请参阅func Dial 
// 参数。
// 
// Listen使用上下文。内部背景；要指定上下文，请使用
// ListenConfig。听
func Listen(network, address string) (Listener, error) {
	var lc ListenConfig
	return lc.Listen(context.Background(), network, address)
}

// ListenPacket在本地网络地址上宣布。
// 
// 网络必须是“udp”、“udp4”、“udp6”、“unixgram”或IP 
// 传输。IP传输为“IP”、“ip4”或“ip6”，后跟一个冒号和一个文字协议号或协议名，如
// /“IP:1”或“IP:icmp”。
// 
// 对于UDP和IP网络，如果地址参数中的主机为
// 空或未指定的文字IP地址，则ListenPacket将在
// 上侦听本地系统的所有可用IP地址，但多播IP 
// 地址除外。
// 要仅使用IPv4，请使用网络“udp4”或“ip4:proto”。
// 该地址可以使用主机名，但不建议使用，因为它最多会为主机的一个IP 
// 地址创建一个侦听器。
// 如果地址参数中的端口为空或“0”，如
// “127.0.0.1:”或“[：：1]：0”，则会自动选择端口号。
// PacketConn的LocalAddr方法可用于发现
// 所选端口。
// 
// 有关网络和地址的说明，请参阅func Dial 
// 参数。
// 
// ListenPacket使用上下文。内部背景；要指定上下文，请使用
// ListenConfig。ListenPacket。
func ListenPacket(network, address string) (PacketConn, error) {
	var lc ListenConfig
	return lc.ListenPacket(context.Background(), network, address)
}
